#include <utils/statement.h>


namespace AccountManagerUtils {
	
//! Default constructor
Statement::Statement() {}

//! Copy constructor
Statement::Statement(const Statement& rhs) :
	month_(rhs.month_), items_(rhs.items_) {}

//! Constructor from a month and a list of items
Statement::Statement(const QDate& month, const ItemList& items) : month_(month), items_(&items) {}

//! Destructor
Statement::~Statement() {}

//! Copy constructor
Statement& Statement::operator=(const Statement& rhs) {
	if ( &rhs != this ) {
		month_ = rhs.month_;
		items_ = rhs.items_;
	}
	return *this;
}

//! Accessor to the month of the bank statement (read only)
const QDate& Statement::month() const {
	return month_;
}

//! Accessor to the list of items of the bank statement (read only)
const ItemList& Statement::items() const {
	if ( items_ != NULL ) {
		return *items_;
	}
	return items_dummy_;
}

//! Compute the sum of all the items' value for this statement
Item::ValueType Statement::delta() const {
	Item::ValueType res = (Item::ValueType)0;
	if ( items_ != NULL ) {
		res += items_->value();
	}
	return res;
}

//! Compute the sum of all the items' value for this statement
Item::ValueType Statement::delta(const QDate& date) const {
	Item::ValueType res = (Item::ValueType)0;
	if ( items_ != NULL ) {
		for ( ItemList::const_iterator it = items_->begin(); it != items_->end(); ++it ) {
			if ( it->date() >= date ) continue;
			res += it->value();
		}
	}
	return res;
}

//===========================================================
	
//! Default constructor
StatementCollection::StatementCollection( Item::ValueType first_balance ) : first_balance_(first_balance) {}

//! Copy constructor
StatementCollection::StatementCollection(const StatementCollection& rhs) :
	map_(rhs.map_), first_balance_(rhs.first_balance_), start_date_(rhs.start_date_), end_date_(rhs.end_date_) {}

//! Destructor
StatementCollection::~StatementCollection() {}

//! Copy constructor
StatementCollection& StatementCollection::operator=(const StatementCollection& rhs) {
	if ( &rhs != this ) {
		map_ = rhs.map_;
		first_balance_ = rhs.first_balance_;
		start_date_ = rhs.start_date_;
		end_date_ = rhs.end_date_;
	}
	return *this;
}

//! Add an item to the appropriate statement
/*!
 *  If statement did not yet exist, it is created
 */
bool StatementCollection::add_item(const Item& item, bool allow_multiples) {
	// Try to find an existing statement
	QDate date = item.date();
	date.setDate(date.year(), date.month(), 1);
	StatementMap::iterator it = map_.find( date );

	// If there is no such statement, create it
	if ( it == map_.end() ) {
		ItemList dummy_list;
		it = map_.insert(date,dummy_list);
		if ( it == map_.end() ) {
			return false;
		}
	}
	
	// Test for multiples
	if ( allow_multiples == false ) {
		for ( ItemList::iterator itl = it->begin(); itl != it->end(); ++itl ) {
			if ( (*itl) != item ) continue;
			return false;
		}
	}

	// Add item
	it->append( item );
	if ( start_date_ > date ) start_date_ = date;
	if ( end_date_ < date ) end_date_ = date;
	return true;
}

//! Remove an item from the appropriate statement
/*!
 *  If statement no more has any item, it is destroyed
 */
bool StatementCollection::remove_item(const Item& item, bool remove_all) {
	// Try to find an existing statement
	QDate date = item.date();
	date.setDate(date.year(), date.month(), 1);
	StatementMap::iterator it = map_.find( date );

	// If there is no such statement, done
	if ( it == map_.end() ) {
		return false;
	}
	
	// Test the items
	bool res = false;
	for ( ItemList::iterator itl = it->begin(); itl != it->end(); ++itl ) {
		if ( (*itl) != item ) continue;
		res = true;
		it->erase(itl);
		if ( remove_all == false ) {
			break;
		}
	}

	// Test for empty lists
	if ( it->empty() == true ) {
		map_.erase( it );
		if ( start_date_ != end_date_ ) {
			if ( start_date_ == date ) {
				date.addMonths(1);
				it = map_.find( date );
				while ( ( it == map_.end() ) && ( date < end_date_ ) ) {
					date.addMonths(1);
					it = map_.find( date );
				}
				start_date_ = date;
			}
			else if ( end_date_ == date ) {
				date.addMonths(-1);
				it = map_.find( date );
				while ( ( it == map_.end() ) && ( date > start_date_ ) ) {
					date.addMonths(-1);
					it = map_.find( date );
				}
				end_date_ = date;
			}
		}
	}

	// Add item
	return res;
}

//! Test whether item exists in the appropriate statement
bool StatementCollection::has_item(const Item& item) const {
	// Try to find an existing statement
	QDate date = item.date();
	date.setDate(date.year(), date.month(), 1);
	StatementMap::iterator it = map_.find( date );

	// If there is no such statement, done
	if ( it == map_.end() ) {
		return false;
	}
	
	// Test for existing item
	for ( ItemList::iterator itl = it->begin(); itl != it->end(); ++itl ) {
		if ( (*itl) != item ) continue;
		return true;
	}

	// No existing item could be found
	return false;
}

//! Test whether item exists in the appropriate statement
int StatementCollection::item_count(const Item& item) const {
	// Try to find an existing statement
	QDate date = item.date();
	date.setDate(date.year(), date.month(), 1);
	StatementMap::iterator it = map_.find( date );

	// If there is no such statement, done
	if ( it == map_.end() ) {
		return false;
	}
	
	// Test for existing item
	int res = 0;
	for ( ItemList::iterator itl = it->begin(); itl != it->end(); ++itl ) {
		if ( (*itl) != item ) continue;
		++res;
	}
	return res;
}

//! Accessor to the first date where an item was registered on this statement collection
QDate StatementCollection::start_date() const {
	return start_date_;
}

//! Accessor to the last date where an item was registered on this statement collection
QDate StatementCollection::end_date() const {
	return start_date_;
}

//! Accessor to the balance at given date
Item::ValueType StatementCollection::balance(const QDate& date) const {
	// Try to find an existing statement
	QDate month = date;
	month.setDate(month.year(), month.month(), 1);

	// Update balance with all previous months
	Item::ValueType res = first_balance_;
	if ( month < start_date_ ) return res;
	for ( StatementMap::const_iterator it = map_.begin(); it != map_.end(); ++it ) {
		// Check date
		const QDate& cur_date = it.key();
		if ( month < cur_date ) {
			continue;
		}

		// Setup statement
		Statement statement;
		statement.month_ = cur_date;
		statement.items_ = &(*it);

		// Update balance
		if ( month == cur_date ) {
			res += statement.delta(date);
		}
		else {
			res += statement.delta();
		}
	}

	// Return balance
	return res;
}

//! Access the statement at given month
Statement StatementCollection::statement(const QDate& month) const {
	// Try to find an existing statement
	QDate date = month;
	date.setDate(date.year(), date.month(), 1);
	StatementMap::iterator it = map_.find( date );

	// Setup statement
	Statement res;
	res.month_ = date;
	if ( it != map_.end() ) {
		res.items_ = &(*it);
	}

	// Return statement
	return res;
}

//===========================================================

//! Initialize the statement lib instance
StatementLib* StatementLib::instance_ = NULL;

//! Find a statement collection associated to given bank account, if any
StatementCollection* StatementLib::find_statement_collection(const Account& account) {
	AccountMap::iterator it = instance()->map_.find( &account );
	if ( it == instance()->map_.end() ) {
		return NULL;
	}
	return &(*it);
}

//! Find or create a statement collection associated to given bank account
StatementCollection* StatementLib::create_statement_collection(const Account& account, Item::ValueType first_balance) {
	AccountMap::iterator it = instance()->map_.find( &account );
	if ( it == instance()->map_.end() ) {
		instance()->map_.insert( &account, StatementCollection(first_balance) );
		it = instance()->map_.find( &account );
	}
	return &(*it);
}

//! Find a statement associated to given bank account at given month
Statement StatementLib::find_statement(const Account& account, const QDate& month) {
	// Try to find statement collection
	StatementCollection* collection = find_statement_collection(account);

	// Create a dummy statement if there is no such collection
	if ( collection == NULL ) {
		QDate date = month;
		date.setDate(date.year(), date.month(), 1);
		Statement res;
		res.month_ = date;
		return res;
	}

	// Return statement otherwise
	return collection->statement( month );
}

//! Destructor
StatementLib::~StatementLib() {}

//! Constructor
StatementLib::StatementLib() {}

//! Find or create the instance of StatementLib
StatementLib* StatementLib::instance() {
	if ( instance_ == NULL ) {
		instance_ = new StatementLib();
	}
	return instance_;
}

} // namespace AccountManagerUtils

